﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using iLynx.Common;
using iLynx.Common.Threading;
using iLynx.Common.Threading.Unmanaged;

namespace iLynx.GameOfLife.Logic
{
    public class GameOfLifeLogic : LifeRenderer
    {
        private readonly IThreadManager threadManager;
        private bool isRunning;
        private IWorker logicWorker;
        private TimeSpan logicInterval = TimeSpan.FromMilliseconds(5d);
        // Virus
        //private readonly byte[] liveCellStates = { 0, 0, 1, 1, 0, 0, 1, 0, 0 };
        //private readonly byte[] deadCellStates = { 0, 0, 0, 1, 0, 0, 0, 1, 1 };
        private byte[,] backBuffer;

        //private readonly byte[] liveCellStates = { 0, 0, 1, 1, 0, 0, 0, 0, 1 };
        //private readonly byte[] deadCellStates = { 0, 0, 0, 1, 0, 0, 0, 1, 0 };

        // Default life.
        private readonly byte[] liveCellStates = { 0, 0, 1, 1, 0, 0, 0, 0, 0 };
        private readonly byte[] deadCellStates = { 0, 0, 0, 1, 0, 0, 0, 0, 0 };

        // 0 => Alive, 1 => Alive + 1 Neighbour, 2 => Alive + 2 Neighbours... etc.


        public GameOfLifeLogic(IThreadManager threadManager)
        {
            this.threadManager = Guard.IsNull(() => threadManager);
        }

        public override bool IsRunning
        {
            get { return isRunning; }
            set
            {
                if (value == isRunning) return;
                isRunning = value;
                Stop();
                if (isRunning)
                    Start();
                else
                    Stop();
            }
        }

        private void Start()
        {
            logicWorker = threadManager.StartNew(DoLogic);
        }

        public override void SetInterval(double interval)
        {
            logicInterval = TimeSpan.FromMilliseconds(interval);
        }

        private void Stop()
        {
            if (null == logicWorker) return;
            logicWorker.Wait();
        }

        public override void Clear()
        {
            ChangeSettings(LifeWidth, LifeHeight);
        }

        protected override void SwapBackBufferToFront()
        {
            Buffer.BlockCopy(backBuffer, 0, FrontBuffer, 0, FrontBuffer.Length);
        }

        protected override void SwapFrontBufferToBack()
        {
            Buffer.BlockCopy(FrontBuffer, 0, backBuffer, 0, FrontBuffer.Length);
        }

        #region Overrides of LifeRenderer

        protected override void SetCell(int x, int y, byte state)
        {
            if (!CanInsert(x, y, backBuffer)) return;
            backBuffer[x, y] = state;
        }

        #endregion

        protected override void ChangeSettings(int newWidth, int newHeight)
        {
            BufferLock.EnterWriteLock();
            LifeWidth = newWidth;
            LifeHeight = newHeight;
            FrontBuffer = new byte[LifeWidth, LifeHeight];
            backBuffer = new byte[LifeWidth, LifeHeight];
            BufferLock.ExitWriteLock();
        }

        private void DoLogic()
        {
            while (isRunning)
            {
                Thread.CurrentThread.Join(logicInterval);
                if (!isRunning)
                    return;
                BufferLock.EnterReadLock();
                try
                {
                    if (IsPaused)
                    {
                        SwapBackBufferToFront();
                        continue;
                    }
                    //for (var y = 0; y < height; ++y)
                    Parallel.For(0, LifeHeight, y =>
                    {
                        for (var x = 0; x < LifeWidth; ++x)
                        {
                            var neighbours = GetNeighbours(backBuffer, x, y);
                            var liveNeighbours = neighbours.Count(z => z != 0);
                            var state = backBuffer[x, y];
                            state = 0 == state ? deadCellStates[liveNeighbours] : liveCellStates[liveNeighbours];
                            if (0 != state)
                                state += (byte)liveNeighbours;
                            FrontBuffer[x, y] = state;
                        }
                    }
                    );
                    SwapFrontBufferToBack();
                }
                finally
                {
                    BufferLock.ExitReadLock();
                }
            }
        }

        private IEnumerable<byte> GetNeighbours(byte[,] buffer, int x, int y)
        {
            var result = new byte[8];
            result[0] = GetEntry(buffer, x - 1, y - 1);
            result[1] = GetEntry(buffer, x, y - 1);
            result[2] = GetEntry(buffer, x + 1, y - 1);
            result[3] = GetEntry(buffer, x - 1, y);
            result[4] = GetEntry(buffer, x + 1, y);
            result[5] = GetEntry(buffer, x - 1, y + 1);
            result[6] = GetEntry(buffer, x, y + 1);
            result[7] = GetEntry(buffer, x + 1, y + 1);
            return result;
        }

        private byte GetEntry(byte[,] buffer, int x, int y)
        {
            if (x < 0)
                x = buffer.GetLength(0) - 1;
            if (y < 0)
                y = buffer.GetLength(1) - 1;
            if (x >= buffer.GetLength(0))
                x = 0;
            if (y >= buffer.GetLength(1))
                y = 0;
            return buffer[x, y];
        }
    }
}
